-
Notifications
You must be signed in to change notification settings - Fork 423
Default to padding blinded paths #4213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Default to padding blinded paths #4213
Conversation
TheBlueMatt
commented
Nov 10, 2025
Because they end up both being used to validate a `Bolt12Invoice`, we ended up with a single `OffersContext` both for inclusion in a `Refund` and an `InvoiceRequest`. However, this is ambiguous, and while it doesn't seem like an issue, it also seems like a nice property to only use a given `OffersContext` in one place. Further, in the next commit, we use `OffersContext` to figure out what we're building a blinded path for and changing behavior based on it, so its nice to be unambiguous. Thus, we split the single existing context into `OutboundPaymentInRefund` and `OutboundPaymentInInvReq`.
|
👋 I see @wpaulino was un-assigned. |
65206f0 to
c7d1621
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4213 +/- ##
==========================================
+ Coverage 89.33% 89.35% +0.02%
==========================================
Files 180 180
Lines 138055 139861 +1806
Branches 138055 139861 +1806
==========================================
+ Hits 123326 124972 +1646
- Misses 12122 12298 +176
+ Partials 2607 2591 -16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
🔔 1st Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 3rd Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 4th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 5th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 6th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 7th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 8th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 9th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
|
🔔 10th Reminder Hey @valentinewallace! This PR has been waiting for your review. |
| expected_path_length: usize, | ||
| ) -> bool { | ||
| let introduction_node_id = resolve_introduction_node(lookup_node, path); | ||
| introduction_node_id == expected_introduction_node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We previously had a check that the intro node was encoded as an scid, should we restore that? Or maybe it changed due to the never-compact option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We quite often use non-compact paths now, as we generally try to use them any time where we're not space-constrained, so such a check fails a handful of test.
| /// message, and thus an `Err` is returned. | ||
| /// message, and thus an `Err` is returned. The impact of this may be somewhat muted when | ||
| /// additional padding is added to the blinded path, but this protection is not complete. | ||
| pub struct NodeIdMessageRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, ES: Deref> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to the PR per se, I'm wondering if we can get rid of this struct? I don't see it used anywhere and the docs don't indicate when it should be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent is that someone may want to override the router (eg using the flow's create_offer_builder_using_router).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I still don't think the NodeIdMessageRouter docs make it clear when a user would want to actually do that. I assume it's for more stable blinded paths, i.e. avoiding using scids in a blinded message path ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry I thought you meant how would it be used, not where.
|
👋 The first review has been submitted! Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer. |
c7d1621 to
649ba34
Compare
|
🔔 1st Reminder Hey @valentinewallace! This PR has been waiting for your review. |
valentinewallace
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after squash. May want someone more familiar with the previous padding/dummy hop work to take a look
After much discussion in lightningdevkit#3246 we mostly decided to allow downstream developers to override whatever decisions the `DefaultMessageRouter` makes regarding blinded path selection by providing easy overrides for the selected `OnionMessageRouter`. We did not, however, actually select good defaults for `DefaultMessageRouter`. Here we add those defaults, taking advantage of the `MessageContext` we're given to detect why we're building a blinded path and selecting blinding and compaction parameters based on it. Specifically, if the blinded path is not being built for an offers context, we always use a non-compact blinded path and always pad it to four hops (including the recipient). However, if the blinded path is being built for an `Offers` context which implies it might need to fit in a QR code (or, worse, a payment onion), we reduce our padding and try to build a compact blinded path if possible. We retain the `NodeIdMessageRouter` to disable compact blinded path creation but use the same path-padding heuristic as for `DefaultMessageRouter`.
649ba34 to
8efb3e1
Compare
|
Squashed and updated a few docs/comments: $ git diff-tree -U2 649ba3478 8efb3e171
diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs
index ffdb2f6403..75e2aaf3c5 100644
--- a/lightning/src/onion_message/functional_tests.rs
+++ b/lightning/src/onion_message/functional_tests.rs
@@ -696,5 +696,6 @@ fn test_blinded_path_padding_for_full_length_path() {
#[test]
fn test_blinded_path_compact_padding() {
- // Check that for a blinded path with compact padding, no extra padding is applied.
+ // Check that for a blinded path with non-SCID intermediate hops with compact padding, no extra
+ // padding is applied.
let nodes = create_nodes(4);
@@ -729,5 +730,6 @@ fn test_blinded_path_compact_padding() {
#[test]
fn test_compact_blinded_path_compact_padding() {
- // Check that for a blinded path with compact padding, no extra padding is applied.
+ // Check that for a blinded path with SCID intermediate hops with compact padding, no extra
+ // padding is applied.
let nodes = create_nodes(4);
diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs
index d6d4314196..03fc8dd4f7 100644
--- a/lightning/src/onion_message/messenger.rs
+++ b/lightning/src/onion_message/messenger.rs
@@ -773,4 +773,7 @@ where
/// paths.
///
+/// This may be useful in cases where you want a long-lived blinded path and anticipate channel(s)
+/// may close, but connections to specific peers will remain stable.
+///
/// This message router can only route to a directly connected [`Destination`].
/// |
|
✅ Added second reviewer: @wpaulino |
| Some(&OffersContext::OutboundPaymentInRefund { payment_id, nonce, .. }) => { | ||
| if invoice.is_for_refund() { | ||
| invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) | ||
| } else { | ||
| Err(()) | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will need to mention in the release notes that this breaks existing refunds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TLV type matches for refunds, so it breaks issued-but-unresponded-to invoice requests.
| OutboundPaymentInRefund { | ||
| /// Payment ID used when creating a [`Refund`]. | ||
| /// | ||
| /// [`Refund`]: crate::offers::refund::Refund | ||
| /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
| payment_id: PaymentId, | ||
|
|
||
| /// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or | ||
| /// [`InvoiceRequest`] and for deriving their signing keys. | ||
| /// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] and | ||
| /// for deriving its signing keys. | ||
| /// | ||
| /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice | ||
| /// [`Refund`]: crate::offers::refund::Refund | ||
| nonce: Nonce, | ||
| }, | ||
| /// Context used by a [`BlindedMessagePath`] as a reply path for an [`InvoiceRequest`]. | ||
| /// | ||
| /// This variant is intended to be received when handling a [`Bolt12Invoice`] or an | ||
| /// [`InvoiceError`]. | ||
| /// | ||
| /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
| /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice | ||
| /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError | ||
| OutboundPaymentInInvReq { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about OutboundPaymentForOffer and OutboundPaymentForRefund?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, guess I don't feel strongly. Neither should be ambiguous - we don't use invreqs for refunds, I kept it equivalent cause both inv-reqs and refunds are the same "step" in the bolt 12 flow. If you feel like its more consistent with the rest of our code I'm happy to change it.
lightning/src/ln/offers_tests.rs
Outdated
| && matches!(path.introduction_node(), IntroductionNode::DirectedShortChannelId(..)) | ||
| } | ||
|
|
||
| fn check_dummy_hopd_path_length<'a, 'b, 'c>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/hopd/hops
edit: Thought this was a typo. Or is it? If so, would read better as "hopped".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes it sound like the dummy is hopping over the path, but sure 😂
| // We can only pad by a minimum of two bytes (we can only go from no-TLV to a type + length | ||
| // byte). Thus, if there are any intermediate hops that need to be padded by exactly one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a better explanation. Not sure if I quite understand the parenthetical. Is this referring to TLVs inside ForwardTlvs? Are you saying an intermediary node can see how much padding was added and infer something about the next hop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, do you have any suggested better phrasing? The point is we can never pad by a single byte, so if we need to pad by a single byte, we have to instead just pad everything by 1/2.
If we're building a blinded message path with extra dummy hops, we have to ensure we at least hide the length of the data in pre-final hops as otherwise the dummy hops are trivially obvious. Here we do so, taking an extra `bool` parameter to `BlindedMessagePath` constructors to decide whether to pad every hop to the existing `MESSAGE_PADDING_ROUND_OFF` or whether to only ensure that each non-final hop has an identical hop data length. In cases where the `DefaultMessageRouter` opts to use compact paths, it now also selects compact padding, whether short channel IDs are available or not.
8efb3e1 to
f7620fe
Compare